// Copyright 2018 The Cockroach Authors.
// Copyright (c) 2022-present, Shanghai Yunxi Technology Co, Ltd. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This software (KWDB) is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//          http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

package main

import (
	"bytes"
	"go/build"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"gitee.com/kwbasedb/kwbase/pkg/testutils"
)

var expectedA = `# Code generated by prereqs. DO NOT EDIT!

bin/a: a/a.go a/invalid.go a/cgo.go a/ignore.go a/a.c a/a.f a b/b.go b b/vendor/foo.com/bar/bar.go b/vendor/foo.com/bar vendor/foo.com/foo/foo.go vendor/foo.com/foo

a/a.go:
a/invalid.go:
a/cgo.go:
a/ignore.go:
a/a.c:
a/a.f:
a:
b/b.go:
b:
b/vendor/foo.com/bar/bar.go:
b/vendor/foo.com/bar:
vendor/foo.com/foo/foo.go:
vendor/foo.com/foo:
`

var expectedB = `# Code generated by prereqs. DO NOT EDIT!

bin/b: b/b.go b b/vendor/foo.com/bar/bar.go b/vendor/foo.com/bar vendor/foo.com/foo/foo.go vendor/foo.com/foo

b/b.go:
b:
b/vendor/foo.com/bar/bar.go:
b/vendor/foo.com/bar:
vendor/foo.com/foo/foo.go:
vendor/foo.com/foo:
`

var expectedFoo = `# Code generated by prereqs. DO NOT EDIT!

bin/foo: vendor/foo.com/foo/foo.go vendor/foo.com/foo

vendor/foo.com/foo/foo.go:
vendor/foo.com/foo:
`

var expectedSpecialChars = `# Code generated by prereqs. DO NOT EDIT!

bin/specialchars: specialchars/a\[\]\*\?\~ $$%\#.go specialchars

specialchars/a\[\]\*\?\~ $$%\#.go:
specialchars:
`

var expectedTestNoDeps = `# Code generated by prereqs. DO NOT EDIT!

bin/test: test

test:
`
var expectedTestWithDeps = `# Code generated by prereqs. DO NOT EDIT!

bin/test: test test/internal_test.go test/external_test.go vendor/foo.com/foo/foo.go vendor/foo.com/foo vendor/foo.com/baz/baz.go vendor/foo.com/baz

test:
test/internal_test.go:
test/external_test.go:
vendor/foo.com/foo/foo.go:
vendor/foo.com/foo:
vendor/foo.com/baz/baz.go:
vendor/foo.com/baz:
`

type fakeFileInfo struct {
	dir      bool
	basename string
	modtime  time.Time
	contents string
}

func (f *fakeFileInfo) Name() string       { return f.basename }
func (f *fakeFileInfo) Sys() interface{}   { return nil }
func (f *fakeFileInfo) ModTime() time.Time { return f.modtime }
func (f *fakeFileInfo) IsDir() bool        { return f.dir }
func (f *fakeFileInfo) Size() int64        { return int64(len(f.contents)) }
func (f *fakeFileInfo) Mode() os.FileMode  { return 0644 }

type fakeFile struct {
	*strings.Reader
}

func (f *fakeFile) Close() error {
	return nil
}

func TestPrereqs(t *testing.T) {
	defer func(origCtx build.Context) {
		buildCtx = origCtx
	}(buildCtx)

	gopath, err := filepath.Abs("testdata")
	if err != nil {
		t.Fatal(err)
	}
	buildCtx.GOPATH = gopath

	const pkg = "testdata/src/example.com"
	absPkg, err := filepath.Abs(pkg)
	if err != nil {
		t.Fatal(err)
	}
	defer func(dir string, err error) {
		_ = os.Chdir(dir)
	}(os.Getwd())
	if err := os.Chdir(pkg); err != nil {
		t.Fatal(err)
	}

	testutils.RunTrueAndFalse(t, "symlink", func(t *testing.T, symlink bool) {
		if symlink {
			tempDir, cleanup := testutils.TempDir(t)
			defer cleanup()

			link := filepath.Join(tempDir, "link")
			if err := os.Symlink(absPkg, link); err != nil {
				t.Fatal(err)
			}

			// You can't chdir into a symlink. Instead, you chdir into the physical
			// path and set the PWD to the logical path. (At least, this is the hack
			// that most shells employ, and os.Getwd respects it.)
			if err := os.Setenv("PWD", link); err != nil {
				t.Fatal(err)
			}
			if cwd, err := os.Getwd(); err != nil {
				t.Fatal(err)
			} else if cwd != link {
				t.Fatalf("failed to chdir into symlink %s (os.Getwd reported %s)", link, cwd)
			}
		}

		for _, tc := range []struct {
			path        string
			exp         string
			includeTest bool
		}{
			{path: "example.com/a", exp: expectedA},
			{path: "./b", exp: expectedB},
			{path: "./a/../b", exp: expectedB},
			{path: "example.com/a/../b", exp: expectedB},
			{path: "example.com/b", exp: expectedB},
			{path: "foo.com/foo", exp: expectedFoo},
			{path: "./vendor/foo.com/foo", exp: expectedFoo},
			{path: "example.com/vendor/foo.com/foo", exp: expectedFoo},
			{path: "example.com/test", exp: expectedTestNoDeps},
			{path: "example.com/test", exp: expectedTestWithDeps, includeTest: true},
		} {
			t.Run(tc.path, func(t *testing.T) {
				var buf bytes.Buffer
				if err := run(&buf, tc.path, tc.includeTest, ""); err != nil {
					t.Fatal(err)
				}
				if e, a := tc.exp, buf.String(); e != a {
					t.Fatalf("expected:\n%s\nactual:\n%s\n", e, a)
				}
			})
		}
	})

	// The test for special character handling is special because creating a
	// filename with these special characters is problematic in some
	// contexts. Notably, we can't create such files on Windows or add them to
	// zips, which in turn prohibits the Cockroach source code from being used in
	// those contexts. To workaround this limitation, we customize buildCtx and
	// override the directory and file operations to overlay a filename containing
	// special characters.
	t.Run("example.com/specialchars", func(t *testing.T) {
		defer func(origCtx build.Context) {
			buildCtx = origCtx
		}(buildCtx)

		srcDir := filepath.Join(buildCtx.GOPATH, "src")
		pkg := "example.com/specialchars"
		pkgPath := filepath.Join(srcDir, pkg)
		fakeContents := "package specialchars\n"

		buildCtx.IsDir = func(path string) bool {
			if path == pkgPath {
				return true
			}
			fi, err := os.Stat(path)
			return err == nil && fi.IsDir()
		}
		buildCtx.ReadDir = func(dir string) ([]os.FileInfo, error) {
			if dir == pkgPath {
				return []os.FileInfo{
					&fakeFileInfo{
						basename: "_ignore.go",
						contents: fakeContents,
					},
					&fakeFileInfo{
						basename: "a[]*?~ $%#.go",
						contents: fakeContents,
					},
				}, nil
			}
			return ioutil.ReadDir(dir)
		}
		buildCtx.OpenFile = func(path string) (io.ReadCloser, error) {
			if filepath.Dir(path) == pkgPath {
				return &fakeFile{
					Reader: strings.NewReader(fakeContents),
				}, nil
			}
			f, err := os.Open(path)
			if err != nil {
				return nil, err // nil interface
			}
			return f, nil
		}

		var buf bytes.Buffer
		if err := run(&buf, pkg, false, ""); err != nil {
			t.Fatal(err)
		}

		exp := expectedSpecialChars
		if e, a := exp, buf.String(); e != a {
			t.Fatalf("expected:\n%s\nactual:\n%s\n", e, a)
		}
	})
}
